( function () {
class Lensflare extends THREE.Mesh {
  constructor() {
    super(Lensflare.Geometry, new THREE.MeshBasicMaterial({
      opacity: 0,
      transparent: true
    }));
    this.type = 'Lensflare';
    this.frustumCulled = false;
    this.renderOrder = Infinity; //

    const positionScreen = new THREE.Vector3();
    const positionView = new THREE.Vector3(); // textures

    const tempMap = new THREE.DataTexture(new Uint8Array(16 * 16 * 3), 16, 16, THREE.RGBFormat);
    tempMap.minFilter = THREE.NearestFilter;
    tempMap.magFilter = THREE.NearestFilter;
    tempMap.wrapS = THREE.ClampToEdgeWrapping;
    tempMap.wrapT = THREE.ClampToEdgeWrapping;
    const occlusionMap = new THREE.DataTexture(new Uint8Array(16 * 16 * 3), 16, 16, THREE.RGBFormat);
    occlusionMap.minFilter = THREE.NearestFilter;
    occlusionMap.magFilter = THREE.NearestFilter;
    occlusionMap.wrapS = THREE.ClampToEdgeWrapping;
    occlusionMap.wrapT = THREE.ClampToEdgeWrapping; // material

    const geometry = Lensflare.Geometry;
    const material1a = new THREE.RawShaderMaterial({
      uniforms: {
        'scale': {
          value: null
        },
        'screenPosition': {
          value: null
        }
      },
      vertexShader:
      /* glsl */
      `

				precision highp float;

				uniform vec3 screenPosition;
				uniform vec2 scale;

				attribute vec3 position;

				void main() {

					gl_Position = vec4( position.xy * scale + screenPosition.xy, screenPosition.z, 1.0 );

				}`,
      fragmentShader:
      /* glsl */
      `

				precision highp float;

				void main() {

					gl_FragColor = vec4( 1.0, 0.0, 1.0, 1.0 );

				}`,
      depthTest: true,
      depthWrite: false,
      transparent: false
    });
    const material1b = new THREE.RawShaderMaterial({
      uniforms: {
        'map': {
          value: tempMap
        },
        'scale': {
          value: null
        },
        'screenPosition': {
          value: null
        }
      },
      vertexShader:
      /* glsl */
      `

				precision highp float;

				uniform vec3 screenPosition;
				uniform vec2 scale;

				attribute vec3 position;
				attribute vec2 uv;

				varying vec2 vUV;

				void main() {

					vUV = uv;

					gl_Position = vec4( position.xy * scale + screenPosition.xy, screenPosition.z, 1.0 );

				}`,
      fragmentShader:
      /* glsl */
      `

				precision highp float;

				uniform sampler2D map;

				varying vec2 vUV;

				void main() {

					gl_FragColor = texture2D( map, vUV );

				}`,
      depthTest: false,
      depthWrite: false,
      transparent: false
    }); // the following object is used for occlusionMap generation

    const mesh1 = new THREE.Mesh(geometry, material1a); //

    const elements = [];
    const shader = LensflareElement.Shader;
    const material2 = new THREE.RawShaderMaterial({
      uniforms: {
        'map': {
          value: null
        },
        'occlusionMap': {
          value: occlusionMap
        },
        'color': {
          value: new THREE.Color(0xffffff)
        },
        'scale': {
          value: new THREE.Vector2()
        },
        'screenPosition': {
          value: new THREE.Vector3()
        }
      },
      vertexShader: shader.vertexShader,
      fragmentShader: shader.fragmentShader,
      blending: THREE.AdditiveBlending,
      transparent: true,
      depthWrite: false
    });
    const mesh2 = new THREE.Mesh(geometry, material2);

    this.addElement = function (element) {
      elements.push(element);
    }; //


    const scale = new THREE.Vector2();
    const screenPositionPixels = new THREE.Vector2();
    const validArea = new THREE.Box2();
    const viewport = new THREE.Vector4();

    this.onBeforeRender = function (renderer, scene, camera) {
      renderer.getCurrentViewport(viewport);
      const invAspect = viewport.w / viewport.z;
      const halfViewportWidth = viewport.z / 2.0;
      const halfViewportHeight = viewport.w / 2.0;
      let size = 16 / viewport.w;
      scale.set(size * invAspect, size);
      validArea.min.set(viewport.x, viewport.y);
      validArea.max.set(viewport.x + (viewport.z - 16), viewport.y + (viewport.w - 16)); // calculate position in screen space

      positionView.setFromMatrixPosition(this.matrixWorld);
      positionView.applyMatrix4(camera.matrixWorldInverse);
      if (positionView.z > 0) return; // lensflare is behind the camera

      positionScreen.copy(positionView).applyMatrix4(camera.projectionMatrix); // horizontal and vertical coordinate of the lower left corner of the pixels to copy

      screenPositionPixels.x = viewport.x + positionScreen.x * halfViewportWidth + halfViewportWidth - 8;
      screenPositionPixels.y = viewport.y + positionScreen.y * halfViewportHeight + halfViewportHeight - 8; // screen cull

      if (validArea.containsPoint(screenPositionPixels)) {
        // save current RGB to temp texture
        renderer.copyFramebufferToTexture(screenPositionPixels, tempMap); // render pink quad

        let uniforms = material1a.uniforms;
        uniforms['scale'].value = scale;
        uniforms['screenPosition'].value = positionScreen;
        renderer.renderBufferDirect(camera, null, geometry, material1a, mesh1, null); // copy result to occlusionMap

        renderer.copyFramebufferToTexture(screenPositionPixels, occlusionMap); // restore graphics

        uniforms = material1b.uniforms;
        uniforms['scale'].value = scale;
        uniforms['screenPosition'].value = positionScreen;
        renderer.renderBufferDirect(camera, null, geometry, material1b, mesh1, null); // render elements

        const vecX = -positionScreen.x * 2;
        const vecY = -positionScreen.y * 2;

        for (let i = 0, l = elements.length; i < l; i++) {
          const element = elements[i];
          const uniforms = material2.uniforms;
          uniforms['color'].value.copy(element.color);
          uniforms['map'].value = element.texture;
          uniforms['screenPosition'].value.x = positionScreen.x + vecX * element.distance;
          uniforms['screenPosition'].value.y = positionScreen.y + vecY * element.distance;
          size = element.size / viewport.w;
          const invAspect = viewport.w / viewport.z;
          uniforms['scale'].value.set(size * invAspect, size);
          material2.uniformsNeedUpdate = true;
          renderer.renderBufferDirect(camera, null, geometry, material2, mesh2, null);
        }
      }
    };

    this.dispose = function () {
      material1a.dispose();
      material1b.dispose();
      material2.dispose();
      tempMap.dispose();
      occlusionMap.dispose();

      for (let i = 0, l = elements.length; i < l; i++) {
        elements[i].texture.dispose();
      }
    };
  }

}

Lensflare.prototype.isLensflare = true; //

class LensflareElement {
  constructor(texture, size = 1, distance = 0, color = new THREE.Color(0xffffff)) {
    this.texture = texture;
    this.size = size;
    this.distance = distance;
    this.color = color;
  }

}

LensflareElement.Shader = {
  uniforms: {
    'map': {
      value: null
    },
    'occlusionMap': {
      value: null
    },
    'color': {
      value: null
    },
    'scale': {
      value: null
    },
    'screenPosition': {
      value: null
    }
  },
  vertexShader:
  /* glsl */
  `

		precision highp float;

		uniform vec3 screenPosition;
		uniform vec2 scale;

		uniform sampler2D occlusionMap;

		attribute vec3 position;
		attribute vec2 uv;

		varying vec2 vUV;
		varying float vVisibility;

		void main() {

			vUV = uv;

			vec2 pos = position.xy;

			vec4 visibility = texture2D( occlusionMap, vec2( 0.1, 0.1 ) );
			visibility += texture2D( occlusionMap, vec2( 0.5, 0.1 ) );
			visibility += texture2D( occlusionMap, vec2( 0.9, 0.1 ) );
			visibility += texture2D( occlusionMap, vec2( 0.9, 0.5 ) );
			visibility += texture2D( occlusionMap, vec2( 0.9, 0.9 ) );
			visibility += texture2D( occlusionMap, vec2( 0.5, 0.9 ) );
			visibility += texture2D( occlusionMap, vec2( 0.1, 0.9 ) );
			visibility += texture2D( occlusionMap, vec2( 0.1, 0.5 ) );
			visibility += texture2D( occlusionMap, vec2( 0.5, 0.5 ) );

			vVisibility =        visibility.r / 9.0;
			vVisibility *= 1.0 - visibility.g / 9.0;
			vVisibility *=       visibility.b / 9.0;

			gl_Position = vec4( ( pos * scale + screenPosition.xy ).xy, screenPosition.z, 1.0 );

		}`,
  fragmentShader:
  /* glsl */
  `

		precision highp float;

		uniform sampler2D map;
		uniform vec3 color;

		varying vec2 vUV;
		varying float vVisibility;

		void main() {

			vec4 texture = texture2D( map, vUV );
			texture.a *= vVisibility;
			gl_FragColor = texture;
			gl_FragColor.rgb *= color;

		}`
};

Lensflare.Geometry = function () {
  const geometry = new THREE.BufferGeometry();
  const float32Array = new Float32Array([-1, -1, 0, 0, 0, 1, -1, 0, 1, 0, 1, 1, 0, 1, 1, -1, 1, 0, 0, 1]);
  const interleavedBuffer = new THREE.InterleavedBuffer(float32Array, 5);
  geometry.setIndex([0, 1, 2, 0, 2, 3]);
  geometry.setAttribute('position', new THREE.InterleavedBufferAttribute(interleavedBuffer, 3, 0, false));
  geometry.setAttribute('uv', new THREE.InterleavedBufferAttribute(interleavedBuffer, 2, 3, false));
  return geometry;
}();

THREE.Lensflare = Lensflare;
THREE.LensflareElement = LensflareElement;
} )();
